import {getBytes, getLines, getMessages, type Message} from './parse.ts';

const DefaultRetryInterval = 3000;
const LastEventId = 'last-event-id';

export interface Option extends RequestInit {
    headers?: Record<string, string>;
    done?: () => void;
    onOpen?: (response: Response) => void;
    onError?: (error: any) => void;
    onMessage?: (data: Message) => void;
    transform?: (data: string) => any;
    onRetry?: (retry: number) => void;
}

export class FetchEventSource {
    /**
     * request
     *
     * @param url url
     * @param option option
     */
    constructor(
        url: string | URL | globalThis.Request,
        option: Option = {}
    ) {
        this.url = url;
        this.option = option;
        this.controller = new AbortController();
        this.option.signal = this.option.signal || this.controller.signal;
    }

    url: string | URL | globalThis.Request;
    option: Option;
    controller: AbortController;

    async start() {
        // 默认 headers
        this.option.headers = {
            'Accept': 'text/event-stream',
            'Content-Type': 'application/json',
            ...this.option.headers
        };

        let retryTimer: number = 0;
        let retryInterval: number = DefaultRetryInterval;

        const create = async () => {
            try {
                const response: Response = await fetch(this.url, {...this.option});
                this.option?.onOpen?.(response);

                // 响应异常
                if (!response.ok) {
                    const accept: string = this.option?.headers?.['Accept'] || '';
                    const contentType: null | string = response.headers.get('content-type');
                    if (!String(contentType).startsWith(accept)) {
                        console.error(`Expected content-type to be ${accept}, Actual: ${contentType}`);
                        this.option?.onError?.(`Expected content-type to be ${accept}, Actual: ${contentType}`);
                    }
                    throw new Error(`HTTP ${response.status}`);
                }

                await getBytes(response.body!, getLines(getMessages(id => {
                    if (id) {
                        // store the id and send it back on the next retry:
                        this.option.headers = {...this.option.headers, 'last-event-id': id};
                    } else {
                        if (this.option.headers && this.option.headers[LastEventId]) {
                            // don't send the last-event-id header anymore:
                            delete this.option.headers[LastEventId];
                        }
                    }
                }, retry => {
                    retryInterval = retry;
                }, this.option.onMessage)));

                this.option.done && this.option.done();
            } catch (error) {
                if (this.controller.signal.aborted) {
                    console.error(error);
                } else {
                    try {
                        const interval: any = this.option?.onError?.(error) ?? retryInterval;
                        window.clearTimeout(retryTimer);
                        retryTimer = window.setTimeout(create, interval);
                    } catch (innerError) {
                        console.error(innerError);
                        window.clearTimeout(retryTimer);
                        this.abort();
                    }
                }
            }
        };
        await create();
    }

    // 中断请求
    abort() {
        this.controller.abort();
    }
}
